using System;
using BilSimser.SharePoint.Common.Service;
using Microsoft.SharePoint;

namespace BilSimser.SharePoint.Common.Data
{
	/// <summary>
	/// The ListProvider is a data access class for accessing 
	/// SharePoint lists. It retrieves list data through additional
	/// classes like <see cref="SharePointListItem"/> and <see cref="SharePointListDescriptor"/>
	/// </summary>
	public class SharePointListProvider
	{
		#region Fields

		private SPWeb web = null;
		
		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="SharePointListProvider"/> class.
		/// </summary>
		/// <param name="web">The web.</param>
		public SharePointListProvider (SPWeb web)
		{
			this.web = web;
		}
		
		#endregion

		#region Public Methods

		/// <summary>
		/// Deletes the list.
		/// </summary>
		/// <param name="guid">The GUID.</param>
		/// <returns></returns>
		public bool DeleteList(Guid guid)
		{
			bool rc = false;

			try
			{
				web.Lists.Delete(guid);
				// Will throw an exception if the list isn't there
				GetListById(guid);
				rc = false;
			}
			catch (Exception)
			{
				rc = true;
			}

			return rc;
		}

		/// <summary>
		/// Gets the name of the list by.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		public Guid GetListByName(string name)
		{
			try
			{
				SPList list = GetList(name);
				return list.ID;
			}
			catch
			{
				return Guid.Empty;
			}
		}

		/// <summary>
		/// Gets the list by id.
		/// </summary>
		/// <param name="guid">The GUID.</param>
		/// <returns></returns>
		public SharePointListDescriptor GetListById(Guid guid)
		{
			web.Lists.IncludeRootFolder = true;

			SharePointListDescriptor listDescriptor = new SharePointListDescriptor();
			SPList list = GetList(guid.ToString());
			listDescriptor.ListId = list.ID.ToString();
			listDescriptor.ListName = list.Title;

			return listDescriptor;
		}

		/// <summary>
		/// Creates the a generic list with the specified
		/// name. If the list exists, it returns the lists GUID.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <returns></returns>
		public Guid CreateList(string listName)
		{
			Guid guid = Guid.Empty;

			try
			{
				SPList list = web.Lists[listName];
				guid = list.ID;
			}
			catch(Exception)
			{
				guid = web.Lists.Add(listName, string.Empty, SPListTemplateType.GenericList);
			}

			return guid;
		}

		/// <summary>
		/// Gets the list item by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldName">Name of the field.</param>
		/// <param name="fieldValue">The field value.</param>
		/// <returns></returns>
		public SharePointListItem GetListItemByField (string listName, string fieldName, string fieldValue)
		{
			SPList spList = GetList(listName);
			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + GetCAMLClause(spList, fieldName, fieldValue) + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			if (matchingItems.Count > 0)
				return SPListItemtoDescriptor(matchingItems[0]);
			else
				return null;
		}

		/// <summary>
		/// Gets the list item by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <returns></returns>
		public SharePointListItem GetListItemByField (string listName, string[] fieldNames, string[] fieldValues)
		{
			SPList spList = GetList(listName);
			string criteria = "";

			if (fieldNames.Length != fieldValues.Length) throw new Exception("# of values must match # of fields");
			for (int i = 0; i < fieldNames.Length; i++)
			{
				string clause = GetCAMLClause(spList, fieldNames[i], fieldValues[i]);

				if (criteria.Length > 0)
					criteria = "<And>" + criteria + clause + "</And>";
				else
					criteria = clause;
			}

			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + criteria + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			if (matchingItems.Count > 0)
				return SPListItemtoDescriptor(matchingItems[0]);
			else
				return null;
		}

		/// <summary>
		/// Gets the list items by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <returns></returns>
		public SharePointListDescriptor GetListItemsByField (string listName, string fieldNames, string fieldValues)
		{
			return GetListItemsByField(listName, fieldNames, fieldValues, "Eq");
		}

		/// <summary>
		/// Gets the list items by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <param name="queryType">Type of the query.</param>
		/// <returns></returns>
		public SharePointListDescriptor GetListItemsByField (string listName, string fieldNames, string fieldValues, string queryType)
		{
			SPList spList = GetList(listName);
			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + GetCAMLClause(spList, fieldNames, fieldValues, queryType) + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			SharePointListDescriptor sharePointListDescriptor = new SharePointListDescriptor();
			sharePointListDescriptor.ListId = spList.ID.ToString();
			sharePointListDescriptor.SharePointListItems = new SharePointListItem[matchingItems.Count];
			sharePointListDescriptor.ListName = spList.Title;

			for (int i = 0; i < matchingItems.Count; i++)
				sharePointListDescriptor.SharePointListItems[i] = SPListItemtoDescriptor(matchingItems[i]);

			return sharePointListDescriptor;
		}

		/// <summary>
		/// Gets the list items by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <returns></returns>
		public SharePointListDescriptor GetListItemsByField (string listName, string[] fieldNames, string[] fieldValues)
		{
			SPList spList = GetList(listName);
			string criteria = "";

			if (fieldNames.Length != fieldValues.Length) throw new Exception("# of values must match # of fields");
			for (int i = 0; i < fieldNames.Length; i++)
			{
				string clause = GetCAMLClause(spList, fieldNames[i], fieldValues[i]);

				if (criteria.Length > 0)
					criteria = "<And>" + criteria + clause + "</And>";
				else
					criteria = clause;
			}

			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + criteria + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			SharePointListDescriptor sharePointListDescriptor = new SharePointListDescriptor();
			sharePointListDescriptor.ListId = spList.ID.ToString();
			sharePointListDescriptor.SharePointListItems = new SharePointListItem[matchingItems.Count];
			sharePointListDescriptor.ListName = spList.Title;

			for (int i = 0; i < matchingItems.Count; i++)
				sharePointListDescriptor.SharePointListItems[i] = SPListItemtoDescriptor(matchingItems[i]);

			return sharePointListDescriptor;
		}

		/// <summary>
		/// Gets the list itemby ID.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="itemID">The item ID.</param>
		/// <returns></returns>
		public SharePointListItem GetListItembyID (string listName, int itemID)
		{
			SPList spList = GetList(listName);
			SPListItem spListItem = spList.Items.GetItemById(itemID);
			return SPListItemtoDescriptor(spListItem);
		}

		/// <summary>
		/// Gets all list items.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <returns></returns>
		public SharePointListDescriptor GetAllListItems (string listName)
		{
			try
			{
				SPList spList = GetList(listName);

				SharePointListDescriptor sharePointListDescriptor = new SharePointListDescriptor();
				sharePointListDescriptor.ListId = spList.ID.ToString();
				sharePointListDescriptor.ListName = spList.Title;
				sharePointListDescriptor.SharePointListItems = new SharePointListItem[spList.ItemCount];
				for (int i = 0; i < spList.ItemCount; i++)
					sharePointListDescriptor.SharePointListItems[i] = SPListItemtoDescriptor(spList.Items[i]);

				return sharePointListDescriptor;
			}
			catch (Exception)
			{
				return new SharePointListDescriptor();
			}
		}

		/// <summary>
		/// Adds the list item.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="newItem">The new item.</param>
		/// <returns></returns>
		public int AddListItem (string listName, SharePointListItem newItem)
		{
			int rc = 0;

			using(Identity.ImpersonateAppPool())
			{
				SPList spList = GetList(listName);
				spList.ParentWeb.AllowUnsafeUpdates = true;
				SPListItem spListItem = spList.Items.Add();
				UpdateSPListItem(spListItem, newItem);
				spListItem.Update();
				rc = spListItem.ID;
			}
			
			return rc;
		}

		/// <summary>
		/// Deletes the list item.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="itemID">The item ID.</param>
		public void DeleteListItem (string listName, int itemID)
		{
			using(Identity.ImpersonateAppPool())
			{
				web.Lists.IncludeRootFolder = true;
				SPList spList = web.Lists[listName];
				spList.Items.DeleteItemById(itemID);
				spList.Update();
			}
		}

		/// <summary>
		/// Gets a collection of all the lists from a web site.
		/// </summary>
		/// <returns></returns>
		public SharePointListDescriptor[] GetLists()
		{
			web.Lists.IncludeRootFolder = true;

			SharePointListDescriptor[] sharePointListDescriptors = new SharePointListDescriptor[web.Lists.Count];
			for (int i = 0; i < sharePointListDescriptors.Length; i++)
			{
				sharePointListDescriptors[i] = new SharePointListDescriptor();
				sharePointListDescriptors[i].ListId = web.Lists[i].ID.ToString();
				sharePointListDescriptors[i].ListName = web.Lists[i].Title;
				sharePointListDescriptors[i].SharePointListItems = null;
			}

			return sharePointListDescriptors;
		}

		/// <summary>
		/// Updates the list item.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="updateItem">The update item.</param>
		/// <returns></returns>
		public int UpdateListItem (string listName, SharePointListItem updateItem)
		{
			int rc = 0;

			using(Identity.ImpersonateAppPool())
			{
				SPList spList = GetList(listName);
				spList.ParentWeb.AllowUnsafeUpdates = true;
				SPListItem spListItem = spList.Items.GetItemById(updateItem.Id);
				UpdateSPListItem(spListItem, updateItem);
				spListItem.Update();
				rc = spListItem.ID;
			}

			return rc;
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Gets the CAML clause.
		/// </summary>
		/// <param name="spList">The sp list.</param>
		/// <param name="fieldName">Name of the field.</param>
		/// <param name="fieldValue">The field value.</param>
		/// <returns></returns>
		private string GetCAMLClause(SPList spList, string fieldName, string fieldValue)
		{
			return GetCAMLClause(spList, fieldName, fieldValue, "Eq");
		}

		/// <summary>
		/// Gets the CAML clause.
		/// </summary>
		/// <param name="spList">The sp list.</param>
		/// <param name="fieldName">Name of the field.</param>
		/// <param name="fieldValue">The field value.</param>
		/// <returns></returns>
		private string GetCAMLClause (SPList spList, string fieldName, string fieldValue, string queryType)
		{
			string dataType = spList.Fields.GetField(fieldName).TypeAsString;
			
			if (dataType == "DateTime")
				fieldValue = DateTime.Parse(fieldValue).ToString("yyyy-MM-ddTHH:mm:ssZ");
			else
			{
				// Determine if this should be BeginsWith, Contains, or Eq
				// based on position of "*" in the query. This will
				// provide us with like matching
				int wildcardIndex = fieldValue.IndexOf("*");
				if (wildcardIndex == 0)
				{
					queryType = "Contains";
					fieldValue = fieldValue.Remove(wildcardIndex, 1);
				}
				else if (wildcardIndex == (fieldValue.Length - 1))
				{
					queryType = "BeginsWith";
					fieldValue = fieldValue.Remove(wildcardIndex, 1);
				}
			}

			return string.Format(
				"<{0}><FieldRef Name=\"{1}\" /><Value Type=\"{2}\">{3}</Value></{0}>",
				queryType,
				spList.Fields.GetField(fieldName).InternalName,
				dataType,
				fieldValue.ToUpper()
				);
		}

		/// <summary>
		/// Gets the list.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <returns></returns>
		private SPList GetList (string listName)
		{
			SPList list;
			web.Lists.IncludeRootFolder = true;

			if ((listName.Trim().Length == 36) && (new Guid(listName).ToString() == listName))
			{
				list = web.Lists[new Guid(listName)];
			}
			else
			{
				list = web.Lists[listName];
			}

			return list;
		}

		/// <summary>
		/// Converts a <see cref="SPListItem"/> to a <see cref="SharePointListItem"/> object.
		/// </summary>
		/// <param name="spListItem">The SPList item to convert.</param>
		/// <returns></returns>
		private SharePointListItem SPListItemtoDescriptor (SPListItem spListItem)
		{
			SharePointListItem sharePointListItem = new SharePointListItem();
			string fieldName;
			for (int j = 0; j < spListItem.Fields.Count; j++)
				if (!spListItem.Fields[j].Hidden)
					try
					{
						sharePointListItem.Id = spListItem.ID;
						fieldName = spListItem.Fields[j].InternalName;
						string val = (spListItem[fieldName] != null ? spListItem[spListItem.Fields[j].InternalName].ToString() : "");
						sharePointListItem.Add(fieldName, val, spListItem.Fields[j].ReadOnlyField);
					}
					catch (Exception)
					{
					}

			return sharePointListItem;
		}

		/// <summary>
		/// Updates the SP list item.
		/// </summary>
		/// <param name="spListItem">The sp list item.</param>
		/// <param name="sharePointListItem">The list item.</param>
		private void UpdateSPListItem (SPListItem spListItem, SharePointListItem sharePointListItem)
		{
			foreach (SharePointListField field in sharePointListItem.SharePointListFields)
			{
				try
				{
					SPField spField = spListItem.Fields.GetField(field.Name);
					if(!spField.ReadOnlyField)
					{
						switch (spField.Type)
						{
							case SPFieldType.DateTime:
								spListItem[field.Name] = DateTime.Parse(field.Value);
								break;

							default:
								spListItem[field.Name] = field.Value;
								break;
						}
					}
				}
				catch (Exception)
				{
				}
			}
		}

		#endregion
	}
}